﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using AForge.Neuro;
using AForge.Neuro.Learning;

namespace Projekt1SN
{
    public class FeedForwardNetwork: INeuralNetwork
    {
        public const double Alfa = 1.0;//0.8;

        public bool bias;

        private ActivationNetwork network;
        private BackPropagationLearning teacher;
        private int cyclesNo;
        private int outputsCount;
        private double[][] input;
        private double[][] output;
        private const double biasWeight = 0.5;

        public FeedForwardNetwork(ActivationFunctionType functionType, bool bias, int inputsCount, int[] neuronsCount, double learningRate, double momentum, int cyclesNo)
        {
            IActivationFunction activationFunction = CreateActivationFunction(functionType);
            network = new ActivationNetwork(activationFunction, inputsCount, neuronsCount);
            if (!bias)
            {
                ClearThreshold();
            }
            this.cyclesNo = cyclesNo;
            this.bias = bias;
            this.outputsCount = neuronsCount[neuronsCount.Length - 1];

            CreateTechaer(learningRate, momentum);
        }

        public void LoadTrainingData(string trainingData)
        {
            string[] trainingStrings = trainingData.Split(new char[] {'\n'}, int.MaxValue);

            input = new double[trainingStrings.Length][];
            output = new double[trainingStrings.Length][];

            for (int i = 0; i < input.Length; i++)
            {
                input[i] = new double[network.InputsCount];
                output[i] = new double[outputsCount];
            }

            for (int i = 0; i < trainingStrings.Length; i++)
            {
                string[] numbers = trainingStrings[i].Split(new char[] {' '});

                for (int j = 0; j < numbers.Length; j++)
                {
                    numbers[j] = numbers[j].TrimStart('[',';');
                    numbers[j] = numbers[j].TrimEnd(']', ';', '\r');
                    numbers[j] = numbers[j].Replace('.', ',');
                    
                    if (j < numbers.Length - outputsCount)
                    {
                        input[i][j] = double.Parse(numbers[j]);
                    }
                    else
                    {
                        output[i][j - network.InputsCount] = double.Parse(numbers[j]);
                    }
                }
            }
        }

        public void Learn(bool showErrors, bool saveToFile)
        {
            System.IO.StreamWriter file = new System.IO.StreamWriter("leariningLog.txt");
            for (int i = 0; i < cyclesNo; i++)
            {
                if (!bias)
                {
                    ClearThreshold();
                }
                
                double error = teacher.RunEpoch(input, output);
                
                if (!bias)
                {
                    ClearThreshold();
                }
                if (showErrors)
                {
                    Console.WriteLine("Iteration: {0} squared error value: {1}",i,error / input.Length);
                    PrintWeights();
                }
                if (saveToFile)
                {
                    file.WriteLine("Iteration: {0} squared error value: {1}", i, error/input.Length);
                }
            }
            file.Close();
        }

        private void ClearThreshold()
        {
            for (int i = 0; i < network.LayersCount; i++)
            {
                for (int j = 0; j < network[i].NeuronsCount; j++)
                {
                    network[i][j].Threshold = 0;
                }
            }
        }

        public double[][] ShowInput()
        {
            return input;
        }

        public double[][] Run(double[][] inputArray)
        {
            List<double[]> outputList = new List<double[]>();

            
            for (int i = 0; i < inputArray.Length; i++)
            {
               outputList.Add(network.Compute(inputArray[i]));
            }
            return outputList.ToArray();
        }

        public double[][] Run()
        {
            return Run(input);
        }

        public void PrintInputOutput(double[][] input, double[][] output)
        {
            for (int i = 0; i < input.Length; i++)
            {
                Console.Write("\n[ ");

                for (int j = 0; j < input[i].Length; j++)
                {
                    Console.Write(input[i][j].ToString("0.00") + " ");
                }

                Console.Write("] \t Output: ");

                for (int j = 0; j < output[i].Length; j++)
                {
                    Console.Write(output[i][j].ToString("0.0000") + " ");
                }
            }
            Console.WriteLine();
        }

        public void LoadInput(string inputString)
        {
            string[] testStrings = inputString.Split(new char[] { '\n' }, int.MaxValue);

            input = new double[testStrings.Length][];
            
            for (int i = 0; i < input.Length; i++)
            {
                input[i] = new double[network.InputsCount];
            }

            for (int i = 0; i < testStrings.Length; i++)
            {
                string[] numbers = testStrings[i].Split(new char[] { ' ' });

                for (int j = 0; j < numbers.Length; j++)
                {
                    numbers[j] = numbers[j].TrimStart('[', ';');
                    numbers[j] = numbers[j].TrimEnd(']', ';', '\r');
                    numbers[j] = numbers[j].Replace('.', ',');

                    input[i][j] = double.Parse(numbers[j]);
                }
            }
        }

        public void PrintWeights()
        {
            for (int i = 0; i < network.LayersCount; i++)
            {
                for (int j = 0; j < network[i].NeuronsCount; j++)
                {
                    for (int k = 0; k < network[i][j].InputsCount; k++)
                    {
                        Console.WriteLine("Layer: {0}  Neuron: {1}  WeightNumber: {2}  WeightValue: {3} Threshold: {4}", i, j, k, network[i][j][k], network[i][j].Threshold);
                    }
                }
            }
        }

        private void CreateTechaer(double learningRate,double momentum)
        {
 	        teacher = new BackPropagationLearning(network);
            teacher.LearningRate = learningRate;
            teacher.Momentum = momentum;
        }

        private IActivationFunction CreateActivationFunction(ActivationFunctionType functionType)
        {
            if (functionType == ActivationFunctionType.Unipolar)
            {
                return new SigmoidFunction(Alfa);
            }
            else if (functionType == ActivationFunctionType.Bipolar)
            {
                return new BipolarSigmoidFunction(Alfa);
            }
            return null;
        }
    }
}
